/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description: UDK mempzone source file
 * Author: -
 * Create: 2021.5.12
 */
#include <stdint.h>
#include <stdbool.h>

#include "udk_common.h"
#include "udk_ops.h"
#include "udk.h"
#include "udk_mcfg.h"
#include "udk_log.h"
#include "udk_malloc.h"
#include "udk_mheap.h"

#include "udk_memzone.h"

static inline const struct udk_memzone *udk_memzone_lookup_thread_unsafe(const char *name)
{
    struct udk_mem_config *mcfg;
    struct udk_fbarray *arr;
    const struct udk_memzone *mz = NULL;
    int i = 0;

    /* get pointer to global configuration */
    mcfg = udk_get_configuration()->mem_config;
    arr = &mcfg->memzones;

    /* the algorithm is not optimal (linear), but there are few zones and this function should be called at init only */
    i = udk_fbarray_find_next_used(arr, 0);
    while (i >= 0) {
        mz = udk_fbarray_get(arr, i);
        if (mz->addr != NULL && !strncmp(name, mz->name, UDK_MEMZONE_NAMESIZE)) {
            return mz;
        }
        i = udk_fbarray_find_next_used(arr, i + 1);
    }
    return NULL;
}

static const struct udk_memzone *udk_memzone_reserve_aligned_thread_unsafe(const char *name, size_t len,
    int socket_id, uint32_t flags, uint32_t align, uint32_t bound)
{
    struct udk_memzone *mz = NULL;
    struct udk_mem_config *mcfg = NULL;
    struct udk_fbarray *arr = NULL;
    size_t requested_len;
    int mz_idx;
    bool contig = false;
    struct udk_melem melem;
    int ret;

    if (len == 0 && bound == 0) {
        UDK_LOG(ERR, MEMZONE, "Invalid alignment len 0 bound 0 for memzone\n");
        return NULL;
    }

    /* get pointer to global configuration */
    mcfg = udk_get_configuration()->mem_config;
    arr = &mcfg->memzones;

    /* no more room in config */
    if (arr->count >= arr->len) {
        UDK_LOG(ERR, MEMZONE, "No more room for memzone in config\n");
        return NULL;
    }

    if (strlen(name) > sizeof(mz->name) - 1) {
        UDK_LOG(ERR, MEMZONE, "Memzone <%s>: name too long\n", name);
        return NULL;
    }

    /* zone already exist */
    if ((udk_memzone_lookup_thread_unsafe(name)) != NULL) {
        UDK_LOG(ERR, MEMZONE, "Memzone <%s> already exists\n", name);
        return NULL;
    }

    /* if alignment is not a power of two */
    if ((align != 0) && (udk_is_power_of_2(align) == 0)) {
        UDK_LOG(ERR, MEMZONE, "Invalid alignment align: %u\n", align);
        return NULL;
    }

    /* alignment less than cache size is not allowed */
    if (align < UDK_CACHE_LINE_SIZE) {
        align = UDK_CACHE_LINE_SIZE;
    }

    /* align length on cache boundary. Check for overflow before doing so */
    if (len > (size_t)(SIZE_MAX - UDK_CACHE_LINE_MASK)) {
        return NULL;
    }

    len = UDK_ALIGN_CEIL(len, UDK_CACHE_LINE_SIZE);

    /* save minimal requested length */
    requested_len = UDK_MAX((size_t)UDK_CACHE_LINE_SIZE, len);
    /* check that boundary condition is valid */
    if (bound != 0 && (requested_len > bound || udk_is_power_of_2(bound) == 0)) {
        return NULL;
    }

    if ((socket_id != UDK_SOCKET_ID_ANY) && socket_id < 0) {
        return NULL;
    }

    /* only set socket to SOCKET_ID_ANY if we aren't allocating for an external heap. */
    if (!udk_has_hugepages() && socket_id < UDK_MAX_NUMA_NODES) {
        socket_id = UDK_SOCKET_ID_ANY;
    }

    contig = (flags & UDK_MEMZONE_IOVA_CONTIG) != 0;
    /* malloc only cares about size flags, remove contig flag from flags */
    flags &= ~UDK_MEMZONE_IOVA_CONTIG;

    if (len == 0) {
        requested_len = bound;
    }

    /* allocate memory on heap */
    ret = udk_malloc_heap_alloc(NULL, requested_len, socket_id, flags, align, bound, contig, &melem);
    if (ret != 0) {
        UDK_LOG(ERR, MEMZONE, "udk_malloc_heap_alloc failed, requested_len:%zu, mz_name:%s, ret: %d.\n",
            requested_len, name, ret);
        return NULL;
    }

    /* fill the zone in config */
    mz_idx = udk_fbarray_find_next_free(arr, 0);
    if (mz_idx < 0) {
        mz = NULL;
    } else {
        (void)udk_fbarray_set_used(arr, mz_idx);
        mz = udk_fbarray_get(arr, mz_idx);
    }

    if (mz == NULL) {
        UDK_LOG(ERR, MEMZONE, "Cannot find free memzone\n");
        (void)udk_malloc_heap_free(melem.addr);
        return NULL;
    }

    if (strcpy_s(mz->name, sizeof(mz->name), name) != 0) {
        UDK_LOG(ERR, MEMZONE, "strcpy mz->name failed. \n");
        (void)udk_fbarray_set_free(arr, mz_idx);
        (void)udk_malloc_heap_free(melem.addr);
        return NULL;
    }
    mz->addr = melem.addr;
    mz->iova = melem.iova;
    mz->len = requested_len;
    mz->hugepage_sz = melem.page_sz;
    mz->socket_id = melem.socket_id;
    mz->flags = 0;

    return mz;
}

static const struct udk_memzone *udk_memzone_reserve_thread_safe(const char *name, size_t len, int socket_id,
    uint32_t flags, uint32_t align, uint32_t bound)
{
    struct udk_mem_config *mcfg;
    const struct udk_memzone *mz = NULL;

    /* get pointer to global configuration */
    mcfg = udk_get_configuration()->mem_config;

    udk_rwlock_write_lock(&mcfg->mlock);

    mz = udk_memzone_reserve_aligned_thread_unsafe(name, len, socket_id, flags, align, bound);

    udk_rwlock_write_unlock(&mcfg->mlock);

    return mz;
}

const struct udk_memzone *udk_memzone_lookup(const char *name)
{
    if (g_udk_reg_ops.memzone_lookup != NULL) {
        return g_udk_reg_ops.memzone_lookup(name);
    }

    struct udk_mem_config *mcfg;
    const struct udk_memzone *memzone = NULL;

    mcfg = udk_get_configuration()->mem_config;

    udk_rwlock_read_lock(&mcfg->mlock);

    memzone = udk_memzone_lookup_thread_unsafe(name);

    udk_rwlock_read_unlock(&mcfg->mlock);

    return memzone;
}

const struct udk_memzone *udk_memzone_reserve(const char *name, size_t len, int socket_id, uint32_t flags)
{
    if (g_udk_reg_ops.memzone_reserve != NULL) {
        return g_udk_reg_ops.memzone_reserve(name, len, socket_id, flags);
    }

    return udk_memzone_reserve_thread_safe(name, len, socket_id, flags, UDK_CACHE_LINE_SIZE, 0);
}

int udk_memzone_free(const struct udk_memzone *mz)
{
    if (g_udk_reg_ops.memzone_free != NULL) {
        return g_udk_reg_ops.memzone_free(mz);
    }

    struct udk_mem_config *mcfg = NULL;
    struct udk_fbarray *arr = NULL;
    struct udk_memzone *found_mz = NULL;
    int ret = 0;
    void *addr = NULL;
    unsigned idx;

    if (mz == NULL) {
        return -EINVAL;
    }

    mcfg = udk_get_configuration()->mem_config;
    arr = &mcfg->memzones;

    udk_rwlock_write_lock(&mcfg->mlock);

    idx = (unsigned int)udk_fbarray_find_idx(arr, mz);
    found_mz = udk_fbarray_get(arr, idx);
    if (found_mz == NULL) {
        ret = -EINVAL;
    } else if (found_mz->addr == NULL) {
        UDK_LOG(ERR, MEMZONE, "Memzone is not allocated\n");
        ret = -EINVAL;
    } else {
        addr = found_mz->addr;
        (void)memset_s(found_mz, sizeof(*found_mz), 0, sizeof(*found_mz));
        (void)udk_fbarray_set_free(arr, idx);
    }

    udk_rwlock_write_unlock(&mcfg->mlock);

    if (addr != NULL) {
        udk_free(addr);
    }

    return ret;
}

const struct udk_memzone *udk_memzone_reserve_aligned(const char *name, size_t len,
    int socket_id, uint32_t flags, uint32_t align)
{
    if (g_udk_reg_ops.memzone_reserve_aligned != NULL) {
        return g_udk_reg_ops.memzone_reserve_aligned(name, len, socket_id, flags, align);
    }

    return udk_memzone_reserve_thread_safe(name, len, socket_id, flags, align, 0);
}

int udk_memzone_init(void)
{
    struct udk_mem_config *mcfg;
    int ret = 0;

    mcfg = udk_get_configuration()->mem_config;

    udk_rwlock_write_lock(&mcfg->mlock);

    if (udk_process_type() == UDK_PROC_PRIMARY &&
            (udk_fbarray_init(&mcfg->memzones, "memzone", UDK_MAX_MEMZONE, sizeof(struct udk_memzone)) != 0)) {
        UDK_LOG(ERR, MEMZONE, "Cannot allocate memzone list\n");
        ret = -1;
        goto unlock;
    } else if (udk_process_type() == UDK_PROC_SECONDARY && (udk_fbarray_attach(&mcfg->memzones) != 0)) {
        UDK_LOG(ERR, MEMZONE, "Cannot attach to memzone list\n");
        ret = -1;
        goto unlock;
    }

unlock:
    udk_rwlock_write_unlock(&mcfg->mlock);

    return ret;
}

void udk_memzone_deinit(void)
{
    struct udk_mem_config *mcfg;

    mcfg = udk_get_configuration()->mem_config;

    udk_rwlock_write_lock(&mcfg->mlock);

    if (udk_process_type() == UDK_PROC_PRIMARY && (udk_fbarray_destroy(&mcfg->memzones) != 0)) {
        UDK_LOG(ERR, MEMZONE, "Cannot destroy memzone list\n");
    }

    udk_rwlock_write_unlock(&mcfg->mlock);
}

static void udk_dump_memzone_single(const struct udk_memzone *mz, void *arg)
{
    struct udk_mem_config *mcfg = udk_get_configuration()->mem_config;
    int mz_idx;
    FILE *f = arg;

    mz_idx = udk_fbarray_find_idx(&mcfg->memzones, mz);

    (void)fprintf(f, "Zone %d: name:<%s>, len:0x%zx, virt:0x%lx, socket_id:%d, flag:0x%x\n",
        mz_idx, mz->name, mz->len, udk_get_ptr(mz->addr), mz->socket_id, mz->flags);
}

void udk_memzone_walk(void (*func)(const struct udk_memzone *, void *), void *arg)
{
    struct udk_mem_config *mcfg = udk_get_configuration()->mem_config;
    struct udk_fbarray *arr = NULL;
    struct udk_memzone *mz = NULL;
    int i;

    udk_rwlock_read_lock(&mcfg->mlock);

    i = udk_fbarray_find_next_used(arr, 0);
    while (i >= 0) {
        mz = udk_fbarray_get(arr, i);
        (*func)(mz, arg);
        i = udk_fbarray_find_next_used(arr, i + 1);
    }

    udk_rwlock_read_unlock(&mcfg->mlock);
}

void udk_memzone_dump(FILE *f)
{
    udk_memzone_walk(udk_dump_memzone_single, f);
}
